﻿using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using NodeBasedSkillEditor.Model;
using NodeBasedSkillEditor.View;
using NodeBasedSkillEditor.Windows;
using XDFLib;

using Num = System.Numerics;
using NodeBasedSkillEditor.Model.CommandHistory;
using System.Data.Common;
using System.Windows.Markup;
using System.Security.Cryptography;

namespace NodeBasedSkillEditor.ViewModel
{
    /// <summary>
    /// 注意，在删除Node时需要先删除所有关联的Link，读数据重建DAG时要先创建Node再创建Link
    /// </summary>
    public class VM_DAG : ObservableObject
    {
        #region enum
        public enum EMouseButtonOp
        {
            None,
            LMBDownAtEmptyArea,
            LMBUpAtEmptyArea,

            LMBDownOnNode,
            LMBUpOnNode,

            LMBDownOnPort,
            LMBUpOnPort,

            LMBDownOnLink,

            RMBDownAtEmptyArea,
            RMBUpAtEmptyArea,

            RMBDownOnNode,
            RMBUpOnNode,

            RMBDownOnPort,
            RMBUpOnPort,

            RMBDownOnLink
        }

        public enum EOperationMode
        {
            None,
            PanCanvas,
            RectangleSelection,
            DragNodes,
            DragPort,
            DAGContextMenu,
            NodeContextMenu
        }

        #endregion

        #region const
        public const float MinZoom = 0.25f;
        public const float MaxZoom = 1f;
        public const float ZoomStep = 0.0625f;
        public const float ZoomFactor = 0.9f;
        #endregion

        #region Binded proerties
        private bool _isDrawingGrid = true;
        public bool IsDrawingGrid {
            get { return _isDrawingGrid; }
            set { _isDrawingGrid = value; OnPropertyChanged(); }
        }

        private float _zoom = 1;
        public float Zoom {
            get { return _zoom; }
            set { _zoom = Math.Clamp(value, MinZoom, MaxZoom); OnPropertyChanged(); }
        }

        private Vector _pan;
        public Vector Pan {
            get { return _pan; }
            set { _pan = value; OnPropertyChanged(); }
        }

        private Point _currMousePos;
        public Point CurrMousePos {
            get { return _currMousePos; }
            set { _currMousePos = value; OnPropertyChanged(); }
        }

        private Point _mousePosInCanvas;

        public Point MousePosInCanvas {
            get { return _mousePosInCanvas; }
            set { _mousePosInCanvas = value; OnPropertyChanged(); }
        }

        private VM_NodeLibView _nodeLibView = new VM_NodeLibView();

        public VM_NodeLibView NodeLibView {
            get { return _nodeLibView; }
            set { _nodeLibView = value; OnPropertyChanged(); }
        }

        #endregion

        public Key NavigationKey { get; set; } = Key.LeftAlt;
        public bool IsNavigationKeyDown { get { return Keyboard.IsKeyDown(NavigationKey); } }
        public static bool IsShiftKeyDown { get { return Keyboard.IsKeyDown(Key.LeftShift); } }
        public static bool IsCtrlKeyDown { get { return Keyboard.IsKeyDown(Key.LeftCtrl); } }

        public static bool IsLMBDown { get { return Mouse.LeftButton == MouseButtonState.Pressed; } }
        public static bool IsRMBDown { get { return Mouse.RightButton == MouseButtonState.Pressed; } }

        public EMouseButtonOp MouseButtonOp { get; private set; } = EMouseButtonOp.None;

        StructProperty<EOperationMode> _currOpMode = new();

        public Point _prevMousePos;
        public Point LMBDownPos { get; private set; }
        public Point RMBUpPos { get; private set; }

        public DirectedAcyclicGraph DAGCtrl { get; private set; }

        readonly RectSelector _rectSelector = new();
        readonly LinkControl _preLink = new();

        private VM_Node _lastSelectedNode;
        private VM_Node _lastNodeAtLMBDown;
        private VM_Node _lastNodeAtRMBDown;

        private VM_Port _lastMBInteractPort;

        private VM_Port _lastPortAtMouseEnter;
        private LinkControl _lastLinkAtLMBDown;

        uint _topNodeId = DAGHelper.MinNodeId;

        public Dictionary<uint, VM_Node> AllNodes { get; private set; } = new Dictionary<uint, VM_Node>();
        //public VM_Node EntryNode { get; private set; }
        private HashSet<VM_Node> _nodesToDel = new();
        public readonly LinkManager LinkManager;
        public bool AllowCyclicFlow => LinkManager.AllowCyclicFlow;

        //private DAG_Data _workingDAGData = new DAG_Data();
        public DAG_Data WorkingDAGData { get; private set; } = new DAG_Data();

        //private DAGUndoMemory _undoMemo = new DAGUndoMemory();

        public event Action<VM_Node> OnAddNode;
        public event Action<VM_Node> OnRemoveNode;
        public event Action<CommandRecord> OnAddCommandRecord;

        public VM_DAG(bool allowCyclicFlow = false)
        {
            _currOpMode.OnValueChanged += OnCurrOpModeChanged;

            LinkManager = new LinkManager(this, WorkingDAGData.Links, allowCyclicFlow);
        }
        public void Initialize(DirectedAcyclicGraph dag)
        {
            DAGCtrl = dag;
        }

        public void RegisterCommandHistoryHandlers()
        {
            UndoManager.Instance.RegisterCommandActions(ECommandType.CreateNode, Undo_CreateNode, Redo_CreateNode);
            UndoManager.Instance.RegisterCommandActions(ECommandType.DeleteNodes, Undo_DeleteNodes, Redo_DeleteNodes);
            UndoManager.Instance.RegisterCommandActions(ECommandType.CreateLink, Undo_CreateLink, Redo_CreateLink);
            UndoManager.Instance.RegisterCommandActions(ECommandType.DeleteLinks, Undo_DeleteLinks, Redo_DeleteLinks);
            UndoManager.Instance.RegisterCommandActions(ECommandType.MoveNodes, Undo_MoveNodes, Redo_MoveNodes);
            UndoManager.Instance.RegisterCommandActions(ECommandType.IncreaseSubPort, Undo_IncreaseSubPort, Redo_IncreaseSubPort);
            UndoManager.Instance.RegisterCommandActions(ECommandType.DeleteSubPort, Undo_DeleteSubPort, Redo_DeleteSubPort);
            UndoManager.Instance.RegisterCommandActions(ECommandType.ChangePortValue, Undo_ChangePortValue, Redo_ChangePortValue);
            UndoManager.Instance.RegisterCommandActions(ECommandType.PasteDagData, Undo_PasteDagData, Redo_PasteDagData);
            UndoManager.Instance.RegisterCommandActions(ECommandType.ChangeNodeDescription, Undo_ChangeNodeDescription, Redo_ChangeNodeDescription);
            UndoManager.Instance.RegisterCommandActions(ECommandType.ChangeEntry, Undo_ChangeEntry, Redo_ChangeEntry);
            UndoManager.Instance.RegisterCommandActions(ECommandType.IncreasePortCluster, Undo_IncreasePortCluster, Redo_IncreasePortCluster);
            UndoManager.Instance.RegisterCommandActions(ECommandType.DeletePortCluster, Undo_DeletePortCluster, Redo_DeletePortCluster);
        }

        public void HandleKeyDown(KeyEventArgs e)
        {
            if (e.Key == Key.Enter && _lastSelectedNode != null)
            {
                CreateInspectorForNode(_lastSelectedNode);
            }
            else if (e.Key == Key.Delete)
            {
                DeleteAllSelectedNodes();
            }
            else if (e.Key == Key.C && IsCtrlKeyDown)
            {
                CopySelectedNodesToClipboard();
            }
            else if (e.Key == Key.X && IsCtrlKeyDown)
            {
                CutSelectedNodesToClipboard();
            }
            else if (e.Key == Key.V && IsCtrlKeyDown)
            {
                PasteFromClipboard();
            }
            else if (e.Key == Key.R && !IsCtrlKeyDown)
            {
                Pan = new Vector();
                DAGCtrl.BackgroundCanvas.ResetPan();
            }
            UpdateOperationMode();
        }

        public void HandleKeyUp(KeyEventArgs e)
        {
            UpdateOperationMode();
        }

        public void OnLMBDown(MouseButtonEventArgs e)
        {
            DAGCtrl.CaptureMouse();
            LMBDownPos = e.GetPosition(DAGCtrl);
            MouseButtonOp = EMouseButtonOp.LMBDownAtEmptyArea;
            UpdateOperationMode();
            e.Handled = true;
        }

        public void OnLMBUp(MouseButtonEventArgs e)
        {
            DAGCtrl.ReleaseMouseCapture();
            MouseButtonOp = EMouseButtonOp.LMBUpAtEmptyArea;
            UpdateOperationMode();
            e.Handled = true;
        }

        public void OnRMBDown(MouseButtonEventArgs e)
        {
            DAGCtrl.CaptureMouse();
            MouseButtonOp = EMouseButtonOp.RMBDownAtEmptyArea;
            e.Handled = true;
        }

        public void OnRMBUp(MouseButtonEventArgs e)
        {
            DAGCtrl.ReleaseMouseCapture();
            RMBUpPos = e.GetPosition(DAGCtrl);
            MouseButtonOp = EMouseButtonOp.RMBUpAtEmptyArea;
            UpdateOperationMode();
            e.Handled = true;
        }

        public void OnMouseMove(MouseEventArgs e)
        {
            CurrMousePos = e.GetPosition(DAGCtrl);
            MousePosInCanvas = DAGCoordToCanvasCoord(CurrMousePos);
            if (_currOpMode.Value == EOperationMode.PanCanvas)
            {
                Pan += CurrMousePos - _prevMousePos;
                DAGCtrl.BackgroundCanvas.PanByMouseMove(CurrMousePos - _prevMousePos);
            }
            else if (_currOpMode.Value == EOperationMode.RectangleSelection)
            {
                var LMBPosOnCanvas = DAGCoordToCanvasCoord(LMBDownPos);
                var CurrMousePosOnCanvas = DAGCoordToCanvasCoord(CurrMousePos);
                var acturalRect = new Rect(LMBPosOnCanvas, CurrMousePosOnCanvas);
                _rectSelector.RangeRect = acturalRect;
            }
            else if (_currOpMode.Value == EOperationMode.DragNodes)
            {
                foreach (var n in AllNodes)
                {
                    if (n.Value.IsSelected)
                    {
                        n.Value.Position += DAGVectorToCanvasVector(CurrMousePos - _prevMousePos);
                    }
                }
            }
            else if (_currOpMode.Value == EOperationMode.DragPort)
            {
                if (_lastMBInteractPort != null)
                {
                    var CurrMousePosOnCanvas = DAGCoordToCanvasCoord(CurrMousePos);
                    if (_lastMBInteractPort.IOType == EPortIOType.Input)
                    {
                        _preLink.StartPoint = CurrMousePosOnCanvas;
                    }
                    else if (_lastMBInteractPort.IOType == EPortIOType.Output)
                    {
                        _preLink.EndPoint = CurrMousePosOnCanvas;
                    }
                }
            }
            _prevMousePos = CurrMousePos;
        }

        public void OnMouseWheel(MouseWheelEventArgs e)
        {
            var mousePos = e.GetPosition(DAGCtrl);
            var deltaZoom = e.Delta > 0 ? 1 / ZoomFactor : ZoomFactor;
            //var deltaZoom = e.Delta > 0 ? ZoomStep : -ZoomStep;
            DAGCtrl.BackgroundCanvas.ZoomByMouseWheel(e);
            ZoomAboutPosition(mousePos, deltaZoom);
        }

        public void OnLMBDownOnNode(MouseButtonEventArgs e, VM_Node node)
        {
            LMBDownPos = e.GetPosition(DAGCtrl);
            MouseButtonOp = EMouseButtonOp.LMBDownOnNode;
            _lastNodeAtLMBDown = node;
            UpdateOperationMode();
            e.Handled = true;
        }

        public void OnLMBUpOnNode(MouseButtonEventArgs e, VM_Node node)
        {
            MouseButtonOp = EMouseButtonOp.LMBUpOnNode;
            UpdateOperationMode();
            _lastNodeAtLMBDown = null;
            e.Handled = true;
        }

        public void OnRMBDownOnNode(MouseButtonEventArgs e, VM_Node node)
        {
            MouseButtonOp = EMouseButtonOp.RMBDownOnNode;
            _lastNodeAtRMBDown = node;
            e.Handled = true;
        }

        public void OnRMBUpOnNode(MouseButtonEventArgs e, VM_Node node)
        {
            MouseButtonOp = EMouseButtonOp.RMBUpOnNode;
            UpdateOperationMode();
            _lastNodeAtRMBDown = null;
            e.Handled = true;
        }

        public void OnDoubleClickNode(MouseButtonEventArgs e, VM_Node node)
        {
            CreateInspectorForNode(node);
            e.Handled = true;
        }

        public void OnLMBDownOnPort(MouseButtonEventArgs e, VM_Port port)
        {
            if (port.IsExpandPort && port.ParentGroup != null)
            {
                var newPortVM = port.Expand();
                var newSubPortRec = new Record_SubPort(newPortVM);
                Record_IncreaseSubPort(newSubPortRec);
            }
            else
            {
                LMBDownPos = e.GetPosition(DAGCtrl);
                MouseButtonOp = EMouseButtonOp.LMBDownOnPort;
                _lastMBInteractPort = port;
                UpdateOperationMode();
            }
            e.Handled = true;
        }

        public void OnLMBUpOnPort(MouseButtonEventArgs e, VM_Port port)
        {
            MouseButtonOp = EMouseButtonOp.LMBUpOnPort;
            UpdateOperationMode();
            e.Handled = true;
        }

        public void OnRMBUpOnPort(MouseButtonEventArgs e, VM_Port port)
        {
            MouseButtonOp = EMouseButtonOp.RMBUpOnPort;
            _lastMBInteractPort = port;
            UpdateOperationMode();
            e.Handled = true;
        }

        public void OnMouseEnterPort(MouseEventArgs e, VM_Port port)
        {
            _lastPortAtMouseEnter = port;
            if ((_currOpMode.Value == EOperationMode.DragPort)
                && _lastMBInteractPort != null && _lastPortAtMouseEnter != null)
            {
                _preLink.IsValid = LinkManager.CanPortsBeLinked(_lastMBInteractPort, _lastPortAtMouseEnter);
            }
            e.Handled = true;
        }

        public void OnMouseLeavePort(MouseEventArgs e, VM_Port port)
        {
            _lastPortAtMouseEnter = null;
            if ((_currOpMode.Value == EOperationMode.DragPort)
                && _lastMBInteractPort != null)
            {
                _preLink.IsValid = true;
            }
            e.Handled = true;
        }

        public void OnLMBDownOnLink(MouseButtonEventArgs e, LinkControl link)
        {
            MouseButtonOp = EMouseButtonOp.LMBDownOnLink;
            _lastLinkAtLMBDown = link;
            UpdateOperationMode();
            e.Handled = true;
        }

        void UpdateOperationMode()
        {
            if (IsNavigationKeyDown)
            {
                if (IsLMBDown)
                {
                    _currOpMode.Value = EOperationMode.PanCanvas;
                }
                else
                {
                    _currOpMode.Value = EOperationMode.None;
                }
            }
            else
            {
                if (MouseButtonOp == EMouseButtonOp.LMBDownAtEmptyArea)
                {
                    _currOpMode.Value = EOperationMode.RectangleSelection;
                }
                else if (MouseButtonOp == EMouseButtonOp.LMBDownOnNode)
                {
                    if (IsShiftKeyDown || IsCtrlKeyDown)
                    {
                        _currOpMode.Value = EOperationMode.RectangleSelection;
                    }
                    else
                    {
                        if (_lastNodeAtLMBDown.IsSelected == false)
                        {
                            DeselectAllNodes();
                            _lastNodeAtLMBDown.IsSelected = true;
                            _lastSelectedNode = _lastNodeAtLMBDown;
                        }
                        _currOpMode.Value = EOperationMode.DragNodes;
                    }
                }
                else if (MouseButtonOp == EMouseButtonOp.LMBUpOnNode)
                {
                    if (_currOpMode.Value == EOperationMode.DragNodes)
                    {
                        var movement = Mouse.GetPosition(DAGCtrl) - LMBDownPos;
                        HashSet<uint> nodes = new HashSet<uint>();
                        foreach (var n in AllNodes)
                        {
                            if (n.Value.IsSelected)
                            {
                                nodes.Add(n.Key);
                            }
                        }
                        if (movement.LengthSquared > 0)
                        {
                            Record_MoveNodes(nodes, movement);
                        }
                    }
                    _currOpMode.Value = EOperationMode.None;
                }
                else if (MouseButtonOp == EMouseButtonOp.RMBUpOnNode)
                {
                    if (_lastNodeAtRMBDown != null && _currOpMode.Value == EOperationMode.None)
                    {
                        _lastNodeAtRMBDown.OpenContextMenu();
                    }
                }
                else if (MouseButtonOp == EMouseButtonOp.LMBDownOnPort)
                {
                    if (IsCtrlKeyDown)
                    {
                        DeleteAllLinksOnLastPortAtLMBDown();
                    }
                    else _currOpMode.Value = EOperationMode.DragPort;
                }
                else if (MouseButtonOp == EMouseButtonOp.LMBUpOnPort)
                {
                    if (_currOpMode.Value == EOperationMode.DragPort)
                    {
                        TryToCreateLink();
                        _currOpMode.Value = EOperationMode.None;
                    }
                }
                else if (MouseButtonOp == EMouseButtonOp.RMBUpOnPort)
                {
                    if (IsCtrlKeyDown)
                    {
                        TryToDeleteLastInteractedPort();
                    }
                    else
                    {
                        _lastMBInteractPort = null;
                    }
                }
                else if (MouseButtonOp == EMouseButtonOp.LMBDownOnLink)
                {
                    if (_lastLinkAtLMBDown != null)
                    {
                        LinkManager.DeleteLink(_lastLinkAtLMBDown);

                        HashSet<(uint, uint)> delLinks = new HashSet<(uint, uint)>();
                        delLinks.Add((_lastLinkAtLMBDown.FromPort.Id, _lastLinkAtLMBDown.ToPort.Id));
                        Record_DeleteLinks(delLinks);

                        if (IsCtrlKeyDown)
                        {
                            _lastLinkAtLMBDown = null;
                            _currOpMode.Value = EOperationMode.None;
                        }
                        else
                        {
                            _lastMBInteractPort = _lastLinkAtLMBDown.FromPort;
                            _lastLinkAtLMBDown = null;
                            _currOpMode.Value = EOperationMode.DragPort;
                        }
                    }
                }
                else if (MouseButtonOp == EMouseButtonOp.LMBUpAtEmptyArea)
                {
                    _currOpMode.Value = EOperationMode.None;
                }
                else if (MouseButtonOp == EMouseButtonOp.RMBUpAtEmptyArea)
                {
                    _currOpMode.Value = EOperationMode.DAGContextMenu;
                }
            }
        }

        void OnCurrOpModeChanged(EOperationMode prevOpMode, EOperationMode currOpMode)
        {
            if (prevOpMode == EOperationMode.PanCanvas)
            {
                EndPanCanvas();
            }
            else if (prevOpMode == EOperationMode.RectangleSelection)
            {
                FinishRectangleSelection();
            }
            else if (prevOpMode == EOperationMode.DragPort)
            {
                EndPreviewLink();
            }

            if (currOpMode == EOperationMode.PanCanvas)
            {
                StartPanCanvas();
            }
            else if (currOpMode == EOperationMode.RectangleSelection)
            {
                StartRectangleSelection();
            }
            else if (currOpMode == EOperationMode.DragPort)
            {
                StartPreviewLink();
            }
            else if (currOpMode == EOperationMode.DAGContextMenu)
            {
                OpenContextMenu();
            }
        }

        void StartPanCanvas()
        {
            DAGCtrl.Cursor = Cursors.ScrollAll;
        }

        void EndPanCanvas()
        {
            DAGCtrl.Cursor = Cursors.Arrow;
        }

        void StartRectangleSelection()
        {
            DAGCtrl.NodeCanvas.Children.Add(_rectSelector);
            DAGCtrl.Cursor = Cursors.Cross;
        }

        void FinishRectangleSelection()
        {
            _lastSelectedNode = null;
            foreach (var node in DAGCtrl.NodeCanvas.Children.OfType<NodeControl>())
            {
                var intersected = _rectSelector.RangeRect.IntersectsWith(node.GetBoundingBox());
                if (IsShiftKeyDown)
                {
                    if (intersected)
                    {
                        node.IsSelected = true;
                        _lastSelectedNode = node.ViewModel;
                    }
                }
                else if (IsCtrlKeyDown)
                {
                    if (intersected)
                    {
                        node.IsSelected = false;
                    }
                }
                else
                {
                    node.IsSelected = intersected;
                    if (node.IsSelected)
                    {
                        _lastSelectedNode = node.ViewModel;
                    }
                }
            }
            DAGCtrl.NodeCanvas.Children.Remove(_rectSelector);
            _rectSelector.Reset();
            DAGCtrl.Cursor = Cursors.Arrow;
        }

        void DeselectAllNodes()
        {
            foreach (var n in AllNodes)
            {
                n.Value.IsSelected = false;
            }
        }

        void DeleteAllLinksOnLastPortAtLMBDown()
        {
            if (_lastMBInteractPort != null)
            {
                var allLinkedIds = LinkManager.GetAllLinkedPortIdsOfPort(_lastMBInteractPort);

                var deleted = LinkManager.DeleteAllLinksOnPort(_lastMBInteractPort);
                if (deleted)
                {
                    HashSet<(uint, uint)> _delLinks = new HashSet<(uint, uint)>();
                    foreach (var l in allLinkedIds)
                    {
                        _delLinks.Add((_lastMBInteractPort.Id, l));
                    }
                    Record_DeleteLinks(_delLinks);
                }
                _lastMBInteractPort = null;
            }
        }

        void StartPreviewLink()
        {
            if (_lastMBInteractPort != null)
            {
                var portPos = _lastMBInteractPort.Position;
                _preLink.ChangeColor(_lastMBInteractPort.PortColor);
                _preLink.StartPoint = portPos;
                _preLink.EndPoint = portPos;
                DAGCtrl.NodeCanvas.Children.Add(_preLink);
            }
        }

        void EndPreviewLink()
        {
            DAGCtrl.NodeCanvas.Children.Remove(_preLink);
            _lastMBInteractPort = null;
        }

        void TryToCreateLink()
        {
            if (_lastMBInteractPort != null && _lastPortAtMouseEnter != null)
            {
                var (newLink, newSubPortVM, replacedLinks) = LinkManager.CreateNewLink(_lastMBInteractPort, _lastPortAtMouseEnter);
                if (newLink != null)
                {
                    Record_SubPort newSubPort = null;
                    if (newSubPortVM != null)
                    {
                        newSubPort = new Record_SubPort(newSubPortVM);
                    }
                    Record_CreateLink(newLink.FromPort.Id, newLink.ToPort.Id, newSubPort, replacedLinks);
                }
                _lastMBInteractPort = null;
                _lastPortAtMouseEnter = null;
            }
        }

        void TryToDeleteLastInteractedPort()
        {
            if (_lastMBInteractPort != null && _lastMBInteractPort.IsNormalPortInGroup)
            {
                var delSubPortRec = new Record_SubPort(_lastMBInteractPort);
                var delLinks = new HashSet<(uint, uint)>();
                var delDestPortIds = LinkManager.GetAllLinkedPortIdsOfPort(_lastMBInteractPort);
                foreach (var pId in delDestPortIds)
                {
                    delLinks.Add((_lastMBInteractPort.Id, pId));
                }
                Record_DeleteSubPort(delSubPortRec, delLinks);

                LinkManager.DeleteAllLinksOnPort(_lastMBInteractPort);
                _lastMBInteractPort.ParentNode.DeletePort(_lastMBInteractPort);
                _lastMBInteractPort = null;
            }
        }

        public void ClearAll()
        {
            DeleteAllNodes();
            _topNodeId = DAGHelper.MinNodeId;
            WorkingDAGData.Clear();
        }

        void DeleteAllNodes()
        {
            _nodesToDel.Clear();
            foreach (var n in AllNodes)
            {
                _nodesToDel.Add(n.Value);
            }
            foreach (var sn in _nodesToDel)
            {
                DeleteNode(sn);
            }
            _nodesToDel.Clear();
        }

        void DeleteAllSelectedNodes()
        {
            _nodesToDel.Clear();
            foreach (var n in AllNodes)
            {
                if (n.Value.IsSelected)
                {
                    _nodesToDel.Add(n.Value);
                }
            }

            bool isDeletingAnyNodes = _nodesToDel.Count > 0;

            if (isDeletingAnyNodes)
            {
                DAG_Data delData = new DAG_Data();
                List<uint> destPortIds = new List<uint>();
                uint entryId = DAGHelper.InvalidNodeId;
                foreach (var nVm in _nodesToDel)
                {
                    if (nVm.LocalId == WorkingDAGData.EntryNodeId)
                    {
                        entryId = nVm.LocalId;
                        SetEntry(DAGHelper.InvalidNodeId, forceChange: true, makeRecord: false);
                    }
                    delData.AddNodeData(nVm.BindNodeData);

                    foreach (var p in nVm.AllPorts)
                    {
                        var pVm = p.Value;

                        destPortIds.Clear();
                        LinkManager.PickAllLinkedPortIdsOfPort(pVm, destPortIds);
                        foreach (var destPortId in destPortIds)
                        {
                            if (pVm.IOType == EPortIOType.Input)
                            {
                                delData.AddLinkData(destPortId, pVm.Id);
                            }
                            else if (pVm.IOType == EPortIOType.Output)
                            {
                                delData.AddLinkData(pVm.Id, destPortId);
                            }
                        }
                    }
                }
                Record_DeleteNodes(delData, entryId);
            }

            foreach (var sn in _nodesToDel)
            {
                DeleteNode(sn);
            }
            _nodesToDel.Clear();
        }

        void CopySelectedNodesToClipboard()
        {
            var copiedDagData = BuildDataForSelectedNodes();
            var dataJson = SwifterJson.XFormatter.ToJson(copiedDagData);
            Clipboard.SetDataObject(dataJson, true);
        }

        void CutSelectedNodesToClipboard()
        {
            CopySelectedNodesToClipboard();
            DeleteAllSelectedNodes();
        }

        void PasteFromClipboard()
        {
            var dataObject = Clipboard.GetDataObject();
            if (dataObject.GetFormats().Length != 0 && dataObject.GetDataPresent(typeof(string)))
            {
                string str = dataObject.GetData(typeof(string)) as string;
                DAG_Data data = SwifterJson.XFormatter.FromJson<DAG_Data>(str);
                RemapIdInDagData(data);

                Num.Vector2 topLeft = new();
                Num.Vector2 bottomRight = new();
                bool first = true;
                foreach (var n in data.AllNodes)
                {
                    var pos = n.Value.Position;
                    if (first)
                    {
                        topLeft = pos;
                        bottomRight = pos;
                        first = false;
                    }
                    else
                    {
                        if (pos.X < topLeft.X)
                        {
                            topLeft.X = pos.X;
                        }
                        else if (pos.X > bottomRight.X)
                        {
                            bottomRight.X = pos.X;
                        }

                        if (pos.Y > topLeft.Y)
                        {
                            topLeft.Y = pos.Y;
                        }
                        else if (pos.Y < bottomRight.Y)
                        {
                            bottomRight.Y = pos.Y;
                        }
                    }
                }

                Num.Vector2 centerPos = (topLeft + bottomRight) / 2;

                var mousePos = Mouse.GetPosition(DAGCtrl);
                mousePos = DAGCoordToCanvasCoord(mousePos);
                var pastePos = mousePos.ToVector2();

                foreach (var n in data.AllNodes)
                {
                    n.Value.Position = n.Value.Position - centerPos + pastePos;
                }

                AddViaDagData(data, true);
                Record_PasteDagData(data);
            }

        }

        DAG_Data BuildDataForSelectedNodes()
        {
            DAG_Data copiedDAGData = new();

            foreach (var n in AllNodes)
            {
                var currNode = n.Value;
                if (currNode.IsSelected)
                {
                    copiedDAGData.AllNodes.Add(currNode.LocalId, currNode.BindNodeData);
                }
            }
            foreach (var n in copiedDAGData.AllNodes)
            {
                var currNode = GetNode(n.Key);
                List<uint> links = new();
                foreach (var p in currNode.AllPorts)
                {
                    // 因为只要保留被选中的Node之间的连接，所以必然是由Node出发到Node结束的链路
                    // 因此只要遍历output端口就足以包含所有需要的连接了
                    var currPort = p.Value;
                    if (currPort.IOType == EPortIOType.Output)
                    {
                        HashSet<uint> destPGIDs;
                        copiedDAGData.Links.TryGetValue(currPort.Id, out destPGIDs);

                        LinkManager.PickAllLinkedPortIdsOfPort(currPort, links);
                        foreach (var destPGID in links)
                        {
                            var destNodeID = (DAGHelper.GetNodeIdFromPortGlobalId(destPGID));
                            if (copiedDAGData.AllNodes.ContainsKey(destNodeID))
                            {
                                if (destPGIDs == null)
                                {
                                    destPGIDs = new();
                                    copiedDAGData.Links.Add(currPort.Id, destPGIDs);
                                }
                                destPGIDs.Add(destPGID);
                            }
                        }
                    }
                }
            }
            return copiedDAGData;
        }

        public void SaveCurrentDAGToFile(string jsonPath, bool compress)
        {
            var strToSave = SwifterJson.XFormatter.ToJson(WorkingDAGData);
            if (compress)
            {
                strToSave = Utilities.CompressString(strToSave);
            }
            Utilities.SaveStringToFile(jsonPath, strToSave);
        }

        public void ReadDAGFile(string jsonPath, bool decompress)
        {
            ClearAll();
            var dagJson = Utilities.ReadStringFromFile(jsonPath);
            if (decompress)
            {
                dagJson = Utilities.DecompressString(dagJson);
            }
            var data = SwifterJson.XFormatter.FromJson<DAG_Data>(dagJson);
            RemapIdInDagData(data);
            AddViaDagData(data);
        }

        void AddViaDagData(DAG_Data data, bool selectOnAdding = false)
        {
            /// 先解除之前选中的节点，以防止发生混选
            if (selectOnAdding)
            {
                DeselectAllNodes();
            }

            foreach (var n in data.AllNodes)
            {
                var currNode = n.Value;
                AddNodeViaNodeData(currNode, selectOnAdding);
            }

            //while (true)
            //{
            //    await Task.Delay(1);
            //    var allReady = true;
            //    foreach (var n in AllNodes)
            //    {
            //        if (!n.Value.IsCtrlReady)
            //        {
            //            allReady = false;
            //        }
            //    }
            //    if (allReady) break;
            //    else continue;
            //}

            //await Task.Delay(1);

            foreach (var l in data.Links)
            {
                var fromPort = GetPort(l.Key);
                foreach (var destGID in l.Value)
                {
                    var toPort = GetPort(destGID);
                    LinkManager.CreateNewLink(fromPort, toPort);
                }
            }

            SetEntry(data.EntryNodeId, makeRecord: false);
        }

        public void SetEntry(uint nodeId, bool forceChange = false, bool makeRecord = true)
        {
            if (WorkingDAGData.EntryNodeId == nodeId) { return; }

            var newEntry = GetNode(nodeId);
            if (newEntry != null && !newEntry.BindNodeData.CanBeEntry())
            {
                VM_MainWindow.Instance.AppenToConsole($"Node {newEntry.NodeName} can not be entry.");
                newEntry = null;
            }

            var oldEntryId = WorkingDAGData.EntryNodeId;
            if (forceChange)
            {
                var oldEntry = GetNode(oldEntryId);
                if (oldEntry != null)
                {
                    oldEntry.IsEntry = false;
                }
                var newEntryId = DAGHelper.InvalidNodeId;
                if (newEntry != null)
                {
                    newEntryId = newEntry.LocalId;
                    newEntry.IsEntry = true;
                }
                WorkingDAGData.EntryNodeId = newEntryId;
                if (makeRecord)
                {
                    Record_ChangeEntry(oldEntryId, newEntryId);
                }
            }
            else if (newEntry != null && newEntry.BindNodeData.CanBeEntry())
            {
                var oldEntry = GetNode(oldEntryId);
                if (oldEntry != null)
                {
                    oldEntry.IsEntry = false;
                }
                var newEntryId = newEntry.LocalId;
                newEntry.IsEntry = true;
                WorkingDAGData.EntryNodeId = newEntryId;
                if (makeRecord)
                {
                    Record_ChangeEntry(oldEntryId, newEntryId);
                }
            }
        }

        public void OnIncreasePortCluster(VM_PortCluster cluster)
        {
            Record_IncreasePortCluster(cluster.ParentGroup.Id);
        }

        public void DeletePortCluster(VM_PortCluster clusterVM)
        {
            var delSubPortRec = new Record_PortCluster(clusterVM);
            var delLinks = new HashSet<(uint, uint)>();
            foreach (var p in clusterVM.AllPorts)
            {
                var delDestPortIds = LinkManager.GetAllLinkedPortIdsOfPort(p);
                foreach (var pId in delDestPortIds)
                {
                    delLinks.Add((p.Id, pId));
                }
                LinkManager.DeleteAllLinksOnPort(p);
            }

            clusterVM.ParentGroup.DeleteCluster(clusterVM);
            Record_DeletePortCluster(delSubPortRec, delLinks);
        }

        /// <summary>注意，此函数会导致data中的所有NodeId从当前DAG的有效TopId开始</summary>
        /// <param name="data"></param>
        void RemapIdInDagData(DAG_Data data)
        {
            Dictionary<uint, uint> idRemap = new();
            Dictionary<uint, NodeData> newAllNodes = new();

            foreach (var n in data.AllNodes)
            {
                var prevId = n.Key;
                var nodeData = n.Value;
                var newId = GenerateNodeId();
                idRemap.Add(prevId, newId);
                nodeData.LocalId = newId;
                newAllNodes.Add(newId, nodeData);
            }
            data.AllNodes = newAllNodes;

            Dictionary<uint, HashSet<uint>> newLinks = new();
            foreach (var l in data.Links)
            {
                var prevPortGID = l.Key;
                var newPortGID = GetRemapedPortId(prevPortGID, idRemap);
                HashSet<uint> newLinkedIds = new();
                newLinks.Add(newPortGID, newLinkedIds);
                foreach (var prevDestGId in l.Value)
                {
                    var newDestGID = GetRemapedPortId(prevDestGId, idRemap);
                    newLinkedIds.Add(newDestGID);
                }
            }
            data.Links = newLinks;
        }

        uint GetRemapedPortId(uint prevPortGID, Dictionary<uint, uint> remap)
        {
            var prevNodeId = DAGHelper.GetNodeIdFromPortGlobalId(prevPortGID);
            uint newNodeId;
            if (remap.TryGetValue(prevNodeId, out newNodeId))
            {
                var portLID = DAGHelper.PortGlobalId_To_PortLocalId(prevPortGID);
                return DAGHelper.PortLocalId_To_PortGlobalId(newNodeId, portLID);
            }
            return prevPortGID;
        }

        void DeleteNode(uint nodeId)
        {
            VM_Node nodeVM;
            if (AllNodes.TryGetValue(nodeId, out nodeVM))
            {
                DeleteNode(nodeVM);
            }
        }

        void DeleteNode(VM_Node nodeVM)
        {
            if (nodeVM.RelativeInspector != null)
            {
                nodeVM.RelativeInspector.CloseWithoutChangeHistory();
            }

            foreach (var p in nodeVM.AllPorts)
            {
                LinkManager.DeleteAllLinksOnPort(p.Value);
            }

            WorkingDAGData.RemoveNodeData(nodeVM.BindNodeData);
            DAGCtrl.NodeCanvas.Children.Remove(nodeVM.BindingNodeCtrl);
            AllNodes.Remove(nodeVM.LocalId);
            OnRemoveNode?.Invoke(nodeVM);
        }

        void OpenContextMenu()
        {
            DAGCtrl.ContextMenu.DataContext = this;
            DAGCtrl.ContextMenu.IsOpen = true;
            _currOpMode.Value = EOperationMode.None;
        }

        void ZoomAboutPosition(Point pos, float deltaZoom)
        {
            // Similarity transformation
            var originOffsetAboutMouse = (Point)Pan - pos;
            var prevZoom = Zoom;
            Zoom *= deltaZoom;
            var offsetScale = Zoom / prevZoom;
            var newOffsetAboutMouse = originOffsetAboutMouse * offsetScale;
            Pan = (Vector)(pos + newOffsetAboutMouse);
        }

        public void OnDrop(DragEventArgs e)
        {
            if (e.Data.GetDataPresent("NodeType"))
            {
                var nodeType = e.Data.GetData("NodeType") as Type;

                if (nodeType != null && nodeType.IsSubclassOf(typeof(NodeData)))
                {
                    var nodeData = Activator.CreateInstance(nodeType) as NodeData;

                    var dagPos = e.GetPosition(DAGCtrl);
                    var canvasPos = DAGCoordToCanvasCoord(dagPos);

                    nodeData.Position = canvasPos.ToVector2();
                    nodeData.LocalId = GenerateNodeId();

                    AddNodeViaNodeData(nodeData);
                    Record_CreateNode(nodeData);
                }
            }
        }

        void AddNodeViaNodeData(NodeData nodeData, bool selectOnAdding = false)
        {
            if (nodeData != null)
            {
                WorkingDAGData.AddNodeData(nodeData);

                var vm = new VM_Node(nodeData, this);
                vm.IsSelected = selectOnAdding;

                AddNodeIntoGraph(vm);
            }
        }

        void AddNodeIntoGraph(VM_Node node)
        {
            //var newNodeCtrl = new NodeCtrl(node);
            var newNodeCtrl = new NodeControl();
            node.InitializeBindingNodeCtrl(newNodeCtrl);
            DAGCtrl.NodeCanvas.Children.Add(newNodeCtrl);
            AllNodes.Add(node.LocalId, node);
            OnAddNode?.Invoke(node);
        }

        #region Coordinate Convert

        public Point DAGCoordToCanvasCoord(Point dagPos)
        {
            return (dagPos - Pan).Divide(Zoom);
        }

        public Point CanvasCoordToDAGCoord(Point canvasPos)
        {
            return canvasPos.Multiply(Zoom) + Pan;
        }

        public Vector DAGVectorToCanvasVector(Vector dagVec)
        {
            return dagVec / Zoom;
        }

        public Vector CanvasVectorToDAGVector(Vector canvasVec)
        {
            return canvasVec * Zoom;
        }
        #endregion

        public VM_Node GetNode(uint nodeId)
        {
            AllNodes.TryGetValue(nodeId, out VM_Node result);
            return result;
        }

        public VM_Port GetPort(uint portId)
        {
            var nodeId = DAGHelper.GetNodeIdFromPortGlobalId(portId);
            VM_Node node = GetNode(nodeId);
            if (node != null)
            {
                var portLocalId = DAGHelper.PortGlobalId_To_PortLocalId(portId);
                return node.GetPort(portLocalId);
            }
            return null;
        }

        #region Inspector
        NodeInspector CreateInspectorForNode(VM_Node nodeVM)
        {
            if (nodeVM.RelativeInspector == null)
            {
                NodeInspector nodeInsp = new();
                nodeInsp.OnAnyValueChanged += OnAnyValueChangedInInspector;
                nodeInsp.OnNodeDescriptionChanged += OnNodeDescriptionChanedInInspector;
                nodeInsp.ChangedNodeViewModel(nodeVM);
                nodeInsp.Show();
                return nodeInsp;
            }
            else
            {
                nodeVM.RelativeInspector.Activate();
                return nodeVM.RelativeInspector;
            }
        }

        void OnAnyValueChangedInInspector(uint portId, object oldValue, object newValue)
        {
            Record_ChangePortValue(portId, oldValue, newValue);
        }

        void OnNodeDescriptionChanedInInspector(uint nodeId, string oldDesc, string newDesc)
        {
            Record_ChangeNodeDescription(nodeId, oldDesc, newDesc);
        }
        #endregion

        uint GenerateNodeId()
        {
            uint newId = DAGHelper.OriginNodeId_To_ValidNodeId(_topNodeId);
            while (AllNodes.ContainsKey(newId))
            {
                _topNodeId++;
                newId = DAGHelper.OriginNodeId_To_ValidNodeId(_topNodeId);
                if (_topNodeId == DAGHelper.MaxNodeId)
                {
                    MessageBox.Show("节点已用尽最大Id！");
                }
            }
            _topNodeId++;
            return newId;
        }

        #region Undo/Redo

        void Record_CreateNode(NodeData data)
        {
            if (OnAddCommandRecord != null)
            {
                var cmdRec = new Cmd_CreateNode(data);
                OnAddCommandRecord(cmdRec);
            }
        }

        public void Undo_CreateNode(CommandRecord command)
        {
            var cmd = command as Cmd_CreateNode;
            if (cmd == null) return;

            var data = cmd.GetNodeData();
            if (data != null)
            {
                DeleteNode(data.LocalId);
            }
        }

        public void Redo_CreateNode(CommandRecord command)
        {
            var cmd = command as Cmd_CreateNode;
            if (cmd == null) return;

            var data = cmd.GetNodeData();
            if (data != null)
            {
                AddNodeViaNodeData(data);
            }
        }

        void Record_DeleteNodes(DAG_Data delData, uint delEntryId)
        {
            if (OnAddCommandRecord != null)
            {
                var cmdRec = new Cmd_DeleteNodes(delData, delEntryId);
                OnAddCommandRecord(cmdRec);
            }
        }

        public void Undo_DeleteNodes(CommandRecord command)
        {
            var cmd = command as Cmd_DeleteNodes;
            if (cmd == null) return;

            var data = cmd.GetDagData();
            if (data != null)
            {
                AddViaDagData(data);
                if (cmd.EntryNodeId != DAGHelper.InvalidNodeId)
                {
                    SetEntry(cmd.EntryNodeId, forceChange: true, makeRecord: false);
                }
            }
        }

        public void Redo_DeleteNodes(CommandRecord command)
        {
            var cmd = command as Cmd_DeleteNodes;
            if (cmd == null) return;

            var data = cmd.GetDagData();
            if (data != null)
            {
                foreach (var n in data.AllNodes)
                {
                    DeleteNode(n.Key);
                }
                if (cmd.EntryNodeId != DAGHelper.InvalidNodeId)
                {
                    SetEntry(DAGHelper.InvalidNodeId, forceChange: true, makeRecord: false);
                }
            }
        }

        void Record_CreateLink(uint fromPortId, uint toPortId, Record_SubPort newSubPort, List<(uint, uint)> replacedLinks)
        {
            if (OnAddCommandRecord != null)
            {
                var cmd = new Cmd_CreateLink(fromPortId, toPortId, newSubPort, replacedLinks);
                OnAddCommandRecord(cmd);
            }
        }

        public void Undo_CreateLink(CommandRecord command)
        {
            var cmd = command as Cmd_CreateLink;
            if (cmd == null) return;

            var fromPort = GetPort(cmd.FromPortId);
            var toPort = GetPort(cmd.ToPortId);
            if (fromPort != null && toPort != null)
            {
                LinkManager.DeleteLink(fromPort, toPort);

                if (cmd.NewSubPort != null)
                {
                    DeleteSubPortFromRecord(cmd.NewSubPort);
                }
            }
            if (cmd.ReplacedLinks != null)
            {
                foreach (var l in cmd.ReplacedLinks)
                {
                    fromPort = GetPort(l.Item1);
                    toPort = GetPort(l.Item2);
                    if (fromPort != null && toPort != null)
                    {

                    }
                    LinkManager.CreateNewLink(fromPort, toPort);
                }
            }
        }

        public void Redo_CreateLink(CommandRecord command)
        {
            var cmd = command as Cmd_CreateLink;
            if (cmd == null) return;

            if (cmd.NewSubPort != null)
            {
                RecorverSubPortFromRecord(cmd.NewSubPort);
            }

            var fromPort = GetPort(cmd.FromPortId);
            var toPort = GetPort(cmd.ToPortId);
            if (fromPort != null && toPort != null)
            {
                LinkManager.CreateNewLink(fromPort, toPort);
            }
        }

        public void Record_DeleteLinks(HashSet<(uint, uint)> delLinkIds)
        {
            if (OnAddCommandRecord != null)
            {
                var cmd = new Cmd_DeleteLinks() { LinkIds = delLinkIds };
                OnAddCommandRecord(cmd);
            }
        }

        public void Undo_DeleteLinks(CommandRecord command)
        {
            var cmd = command as Cmd_DeleteLinks;
            if (cmd == null) return;

            if (cmd.LinkIds != null)
            {
                foreach (var l in cmd.LinkIds)
                {
                    var port1 = GetPort(l.Item1);
                    var port2 = GetPort(l.Item2);

                    if (port1 != null && port2 != null)
                    {
                        LinkManager.CreateNewLink(port1, port2);
                    }
                }
            }
        }

        public void Redo_DeleteLinks(CommandRecord command)
        {
            var cmd = command as Cmd_DeleteLinks;
            if (cmd == null) return;

            if (cmd.LinkIds != null)
            {
                foreach (var l in cmd.LinkIds)
                {
                    var port1 = GetPort(l.Item1);
                    var port2 = GetPort(l.Item2);

                    if (port1 != null && port2 != null)
                    {
                        LinkManager.DeleteLink(port1, port2);
                    }
                }
            }
        }

        public void Record_MoveNodes(HashSet<uint> movedNodeIds, Vector movement)
        {
            if (OnAddCommandRecord != null)
            {
                var cmd = new Cmd_MoveNods(movedNodeIds, movement);
                OnAddCommandRecord(cmd);
            }
        }

        public void Undo_MoveNodes(CommandRecord command)
        {
            var cmd = command as Cmd_MoveNods;
            if (cmd == null) return;

            if (cmd.NodeIds != null)
            {
                foreach (var nId in cmd.NodeIds)
                {
                    var nodeVM = GetNode(nId);
                    nodeVM.Position -= cmd.Movement;
                }
            }
        }

        public void Redo_MoveNodes(CommandRecord command)
        {
            var cmd = command as Cmd_MoveNods;
            if (cmd == null) return;

            if (cmd.NodeIds != null)
            {
                foreach (var nId in cmd.NodeIds)
                {
                    var nodeVM = GetNode(nId);
                    nodeVM.Position += cmd.Movement;
                }
            }
        }

        public void Record_IncreaseSubPort(Record_SubPort newSubPortRec)
        {
            if (OnAddCommandRecord != null)
            {
                var cmd = new Cmd_IncreaseSubPort(newSubPortRec);
                OnAddCommandRecord(cmd);
            }
        }

        public void Undo_IncreaseSubPort(CommandRecord command)
        {
            var cmd = command as Cmd_IncreaseSubPort;
            if (cmd == null) return;

            if (cmd.NewSubPortRec != null)
            {
                DeleteSubPortFromRecord(cmd.NewSubPortRec);
            }
        }

        public void Redo_IncreaseSubPort(CommandRecord command)
        {
            var cmd = command as Cmd_IncreaseSubPort;
            if (cmd == null) return;

            if (cmd.NewSubPortRec != null)
            {
                RecorverSubPortFromRecord(cmd.NewSubPortRec);
            }
        }

        public void Record_DeleteSubPort(Record_SubPort delSubPortRec, HashSet<(uint, uint)> delLinks)
        {
            if (OnAddCommandRecord != null)
            {
                var cmd = new Cmd_DeleteSubPort(delSubPortRec, delLinks);
                OnAddCommandRecord(cmd);
            }
        }

        public void Undo_DeleteSubPort(CommandRecord command)
        {
            var cmd = command as Cmd_DeleteSubPort;
            if (cmd == null) return;

            if (cmd.DeletedSubPort != null)
            {
                RecorverSubPortFromRecord(cmd.DeletedSubPort);

                foreach (var l in cmd.DeletedLinks)
                {
                    var port1 = GetPort(l.Item1);
                    var port2 = GetPort(l.Item2);
                    if (port1 != null && port2 != null)
                    {
                        LinkManager.CreateNewLink(port1, port2);
                    }
                }
            }
        }

        public void Redo_DeleteSubPort(CommandRecord command)
        {
            var cmd = command as Cmd_DeleteSubPort;
            if (cmd == null) return;

            if (cmd.DeletedSubPort != null)
            {
                foreach (var l in cmd.DeletedLinks)
                {
                    var port1 = GetPort(l.Item1);
                    var port2 = GetPort(l.Item2);
                    if (port1 != null && port2 != null)
                    {
                        LinkManager.DeleteLink(port1, port2);
                    }
                }

                DeleteSubPortFromRecord(cmd.DeletedSubPort);
            }
        }

        void Record_ChangePortValue(uint portId, object oldValue, object newValue)
        {
            if (OnAddCommandRecord != null)
            {
                var cmd = new Cmd_ChangePortValue(portId, oldValue, newValue);
                OnAddCommandRecord(cmd);
            }
        }

        public void Undo_ChangePortValue(CommandRecord command)
        {
            var cmd = command as Cmd_ChangePortValue;
            if (cmd == null) return;

            var portVM = GetPort(cmd.PortId);
            if (portVM != null)
            {
                portVM.ChangeValueWithoutChangeHistory(cmd.OldValue);
                //portVM.Value = cmd.OldValue;
            }
        }

        public void Redo_ChangePortValue(CommandRecord command)
        {
            var cmd = command as Cmd_ChangePortValue;
            if (cmd == null) return;

            var portVM = GetPort(cmd.PortId);
            if (portVM != null)
            {
                portVM.ChangeValueWithoutChangeHistory(cmd.NewValue);
                //portVM.Value = cmd.NewValue;
            }
        }

        public void Record_PasteDagData(DAG_Data data)
        {
            if (OnAddCommandRecord != null)
            {
                var cmd = new Cmd_PasteDagData(data);
                OnAddCommandRecord(cmd);
            }
        }

        public void Undo_PasteDagData(CommandRecord command)
        {
            var cmd = command as Cmd_PasteDagData;
            if (cmd == null) return;

            var data = cmd.GetDagData();
            if (data != null)
            {
                foreach (var n in data.AllNodes)
                {
                    DeleteNode(n.Key);
                }
            }
        }

        public void Redo_PasteDagData(CommandRecord command)
        {
            var cmd = command as Cmd_PasteDagData;
            if (cmd == null) return;

            var data = cmd.GetDagData();
            if (data != null)
            {
                AddViaDagData(data, true);
            }
        }

        void Record_ChangeNodeDescription(uint nodeId, string oldDesc, string newDesc)
        {
            if (OnAddCommandRecord != null)
            {
                var cmd = new Cmd_ChangeNodeDescription(nodeId, oldDesc, newDesc);
                OnAddCommandRecord(cmd);
            }
        }

        public void Undo_ChangeNodeDescription(CommandRecord command)
        {
            var cmd = command as Cmd_ChangeNodeDescription;
            if (cmd == null) return;

            var nodeVM = GetNode(cmd.NodeId);
            if (nodeVM != null)
            {
                nodeVM.ChangeDescriptionWithoutChangeHistory(cmd.OldDescription);
            }
        }

        public void Redo_ChangeNodeDescription(CommandRecord command)
        {
            var cmd = command as Cmd_ChangeNodeDescription;
            if (cmd == null) return;

            var nodeVM = GetNode(cmd.NodeId);
            if (nodeVM != null)
            {
                nodeVM.ChangeDescriptionWithoutChangeHistory(cmd.NewDescription);
            }
        }

        void Record_ChangeEntry(uint oldEntryId, uint newEntryId)
        {
            if (OnAddCommandRecord != null)
            {
                var cmd = new Cmd_ChangeEntry(oldEntryId, newEntryId);
                OnAddCommandRecord(cmd);
            }
        }

        public void Undo_ChangeEntry(CommandRecord command)
        {
            var cmd = command as Cmd_ChangeEntry;
            if (cmd == null) return;

            SetEntry(cmd.OldEntryId, forceChange: true, makeRecord: false);
        }

        public void Redo_ChangeEntry(CommandRecord command)
        {
            var cmd = command as Cmd_ChangeEntry;
            if (cmd == null) return;

            SetEntry(cmd.NewEntryId, forceChange: true, makeRecord: false);
        }

        void Record_IncreasePortCluster(uint portClusterGroupId)
        {
            if (OnAddCommandRecord != null)
            {
                var cmd = new Cmd_IncreasePortCluster(portClusterGroupId);
                OnAddCommandRecord(cmd);
            }
        }

        public void Undo_IncreasePortCluster(CommandRecord command)
        {
            var cmd = command as Cmd_IncreasePortCluster;
            if (cmd == null) return;

            var nodeId = DAGHelper.GetNodeIdFromPortGlobalId(cmd.PortClusterGroupId);
            var nodeVM = GetNode(nodeId);

            var clusterGroupLocalId = DAGHelper.PortGlobalId_To_PortLocalId(cmd.PortClusterGroupId);
            var clusterGroupVM = nodeVM.GetPortClusterGroup(clusterGroupLocalId);
            clusterGroupVM.DeleteLastIncreasedCluster();
        }

        public void Redo_IncreasePortCluster(CommandRecord command)
        {
            var cmd = command as Cmd_IncreasePortCluster;
            if (cmd == null) return;

            var nodeId = DAGHelper.GetNodeIdFromPortGlobalId(cmd.PortClusterGroupId);
            var nodeVM = GetNode(nodeId);

            var clusterGroupLocalId = DAGHelper.PortGlobalId_To_PortLocalId(cmd.PortClusterGroupId);
            var clusterGroupVM = nodeVM.GetPortClusterGroup(clusterGroupLocalId);
            clusterGroupVM.IncreaseCluster();
        }

        void Record_DeletePortCluster(Record_PortCluster delClusterRec, HashSet<(uint, uint)> delLinks)
        {
            if (OnAddCommandRecord != null)
            {
                var cmd = new Cmd_DeletePortCluster(delClusterRec, delLinks);
                OnAddCommandRecord(cmd);
            }
        }

        public void Undo_DeletePortCluster(CommandRecord command)
        {
            var cmd = command as Cmd_DeletePortCluster;
            if (cmd == null) return;

            var nodeId = DAGHelper.GetNodeIdFromPortGlobalId(cmd.PortClusterRec.PortClusterGroupId);
            var nodeVM = GetNode(nodeId);

            var clusterGroupLocalId = DAGHelper.PortGlobalId_To_PortLocalId(cmd.PortClusterRec.PortClusterGroupId);
            var clusterGroupVM = nodeVM.GetPortClusterGroup(clusterGroupLocalId);
            var clusterData = SwifterJson.XFormatter.FromJson<PortCluster>(cmd.PortClusterRec.PortClusterDataJson);
            clusterGroupVM.InsertCluster(cmd.PortClusterRec.IndexInPortClusterGroup, clusterData);

            foreach (var l in cmd.DeletedLinks)
            {
                var port1 = GetPort(l.Item1);
                var port2 = GetPort(l.Item2);
                if (port1 != null && port2 != null)
                {
                    LinkManager.CreateNewLink(port1, port2);
                }
            }
        }

        public void Redo_DeletePortCluster(CommandRecord command)
        {
            var cmd = command as Cmd_DeletePortCluster;
            if (cmd == null) return;

            var nodeId = DAGHelper.GetNodeIdFromPortGlobalId(cmd.PortClusterRec.PortClusterGroupId);
            var nodeVM = GetNode(nodeId);

            var clusterGroupLocalId = DAGHelper.PortGlobalId_To_PortLocalId(cmd.PortClusterRec.PortClusterGroupId);
            var clusterGroupVM = nodeVM.GetPortClusterGroup(clusterGroupLocalId);
            var clusterVM = clusterGroupVM.Clusters[cmd.PortClusterRec.IndexInPortClusterGroup];

            foreach (var p in clusterVM.AllPorts)
            {
                LinkManager.DeleteAllLinksOnPort(p);
            }

            clusterGroupVM.DeleteClusterAt(cmd.PortClusterRec.IndexInPortClusterGroup);
        }

        void DeleteSubPortFromRecord(Record_SubPort subPort)
        {
            var portVM = GetPort(subPort.PortId);
            var parentNodeId = DAGHelper.GetNodeIdFromPortGlobalId(subPort.PortId);
            var parentNodeVm = GetNode(parentNodeId);
            parentNodeVm.DeletePort(portVM);
        }

        void RecorverSubPortFromRecord(Record_SubPort subPort)
        {
            var subPortData = SwifterJson.XFormatter.FromJson<BasePort>(subPort.DataJson);
            var parentNodeId = DAGHelper.GetNodeIdFromPortGlobalId(subPort.PortId);
            var parentNodeVm = GetNode(parentNodeId);
            parentNodeVm.RecorverSubPort(subPortData, subPort.GroupLocalId, subPort.IndexInParentGroup);
        }

        #endregion

    }
}
